﻿#ifndef CLIENTSOCKET_H
#define CLIENTSOCKET_H

/*
 * 功能: 1.可以自定义数据头标识和控制字符
 *      2.可以自己加入包长度
 *      3.网断时可以缓存数据(缓存到m_baOutBlock中)(目前没用!!!)
 *      4.可以和Server保持同一个Code(可以加解密数据)
 * 使用方法:
 * 发送方式一:简单明文发送
 *   方法1:
     TcpSocket::
        sendData()<<各种数据      :送入缓存;
        sendData().writeRawData(data,datalen);  :data送入缓存
        send()                  :发送并清空了所有缓存
        //注意:sendData()使用完后,用send()才是一个完整的包过程!!!


        clearOutData()             :手动清空发送缓存

     方法2:
       数据在QByteArray ba内,然后:
         send(ba,code);
       多线程下用connect,很方便解决线程安全问题

     注意:默认,网断时发送的数据都存在了缓存中!
         默认connected信号,应该有send()函数!disconnect信号,应该有clearAll()函数!!


 * 发送方式二:加密发送数据:
     sendUseTransfersCode(ba,baCode)
      将数据加密后发送(控制字符固定为8d),如果没有baCode,则放入发送队列m_listOutCodeData等待
      注意:1.使用时,外部程序应该从m_listOutTransfersCode提取一个Code,使用这个Code得到一个ByteArray,然后用它来进行加密!!!m_listOutTransfersCode为空时,服务器自动会补充!!!
          2.服务器收到控制字符为8d的数据,也应该从m_listOutTransfersCode提取一个Code进行计算后得到ByteArray来解密,m_listOutTransfersCode使用完毕后,及时给客户端补充!!!

   clearAll():删除所有缓存


 * 接受:
    绑定s_readyRead信号后，在槽函数中：

    //while(sock->recievedDataValid())
    //while(!dsData.atEnd())
    {
        switch(sock->m_ucInControlByte)
        {
        case quint8(0x8c):
            qDebug() << "[GameManager]接受到加密Code:" << sock->m_listOutTransfersCode.count();
            SendWithCode(sock);
            return;

        default:
            break;
        }   //switch(sock->m_ucInControlByte)


        sock->recievedData()>>data;
        switch(data)
        {
        / *case 0:
            int datalen = sock->recievedDataSize();
            sock->recievedData() >> netType >> .....;
            ...
            int i = sock->recievedData().readRawData(data,datalen);
            break;
        * /
        default:
            //pGameWindow->scene->ShowDebug(tr("主通信错误%1").arg(data));
            break;
        }   //switch(data)

    }

    注意:
        最大可以收发 quint32 个数据
        不能将几条数据合并发送，只能一条数据对应一个包（标志、控制字符、长度、数据），因为：
          一条数据可以有一个控制字符
        数据处理时,不用while来循环处理,因为解析包时已经将包进行识别处理,1个包1个信号!!

        收数据时,回调了很多信号函数(已经在构造函数中绑定了自定义的处理函数,这些处理函数只是加了this并再次发送,这样在server端好控制一点)
*/

#include <QTcpSocket>
#include <QDataStream>
#include <QMutex>
#include <QTimer>

#include "../../_Global/GlobalClass.h"

class TcpSocket : public QTcpSocket
{
    Q_OBJECT
public:
    explicit TcpSocket(QObject * parent = nullptr);
    virtual ~TcpSocket();

public:
    void connectServer()
    {
        //如果没有连接
        if(state() == QTcpSocket::UnconnectedState)
        {
            if(!m_strIP.isEmpty() && m_unPort != 0)
                this->connectToHost(m_strIP, m_unPort);
        }
    }
//发送函数
    //初始化 发送信息，并返回 已经初始化好的DataStream
    //outControlbyte为加入控制字符,线程不安全!!
    QDataStream &sendData(quint8 outControlbyte = 0);

    //发送数据，并进行结束处理(清空缓存,变量等),线程不安全!!
    //参数:如果网断,是否缓存(如果缓存,请连接s_connected信号并调用send().
    //返回:-1(未知错误);-2(没连接,数据存入缓存);0(没连接,数据丢失,重新连接);>0(发送数目)
    qint64 send(bool bufferFlag = true);

    //发送data(QByteArray),多线程下可connect到这个函数来实现线程安全
    Q_INVOKABLE qint64 send(const QByteArray &data, quint8 outControlbyte = 0, bool bufferFlag = true)
    {
        sendData(outControlbyte).writeRawData(data.constData(), data.length());
        return send(bufferFlag);
    }

public:
    //线程不安全,在主线程中使用!!
    //发送数据使用Code加密
    //参数:ba为数据,baCode为密码
    //使用1:都有参数;使用2:只有ba,加入队列等待Code;使用3:只有baCode,加密发送队列中的1个Data
    //返回true表示发送了8d的加密数据,否则返回false
    bool sendUseTransfersCode(QByteArray &ba, QByteArray baCode = QByteArray())
    {
        /*/如果没有连接
        if(m_AutoConnect == true)
            connectServer();
        */

        //可以直接加密发送
        if(!baCode.isEmpty())   // && m_listOutTransfersCode.count() > 0)
        {
            //如果队列中有数据,拿出第一个数据
            if(!m_listOutCodeData.isEmpty())
            {
                if(!ba.isEmpty())
                    m_listOutCodeData.append(ba);
                ba = m_listOutCodeData.takeFirst();
            }

            //showHex(ba.data(),ba.length());
            if(!ba.isEmpty())
            {
                //加密,使用循环异或
                int i;
                for(i = 0; i < ba.length(); i++)
                    ba[i] = ba.at(i) ^ baCode.at(i % baCode.length());

                //showHex(ba.data(),ba.length());
                //outTransfersCode = -1;
                //m_listOutTransfersCode.takeFirst();
                send(ba,0x8d);
                qDebug() << "[Socket]发送加密数据!";
                return true;
            }
            else
            {
                qDebug() << "[!Socket]无数据发送!";
                return false;
            }
        }
        else
        {
            qDebug() << "[Socket]Data加入队列,等待发送加密数据";
            if(!ba.isEmpty())
                m_listOutCodeData.append(ba);
        }

        return false;
    }



//以下线程不安全
    //清空发送数据(Send完毕)
    //参数:是否连发送缓存数据也清空
    void clearOutData(bool bClearBuffer = false);

    //是否存在发送数据
    bool sendDataValid()
    {
        return m_bOutFlag;
    }
    //返回发送数据
    QByteArray &getOutBlock()
    {
        return m_baOutBlock;
    }
    //返回发送缓存数据
    QByteArray &getSendBuffer()
    {
        return m_baOutBuffer;
    }
    /*
    void setCacheWriteFlag(bool b)
    {
        cacheWriteFlag = b;
    }*/




    //清除所有Code的两个列表
    void clearAllCodeData()
    {
        m_listOutCodeData.clear();
        m_listOutTransfersCode.clear();
    }


    //清除所有
    void clearAll()
    {
        clearOutData(true);
        clearAllCodeData();
    }

//接受函数,线程不安全,在单独的线程(主)中使用!!!
    //数据是否可读(自动识别和跳过 头标识,控制字符和长度等)
    //bool recievedDataValid();
    //返回 单个包的真实数据大小
    //quint32 recievedDataSize();

    //返回读取stream
    QDataStream &recievedData();


//开始心跳发送
    void startKeepAlive(int interval, const char d[], int length)
    {
        m_baHeart.setRawData(d, length);
        //m_nDataHeartLength = length;
        //m_pDataHeart = d;

        m_timerHeart.setSingleShot(false);
        m_timerHeart.setInterval(interval);
        m_timerHeart.start();
    }


private:
    //发送,并清空发送缓存
    qint64 sendAndReset()
    {
        m_baOutBuffer.append(m_baOutBlock);
        if(m_baOutBuffer.length() > 0)
        {
            qint64 sendDataLength = write(m_baOutBuffer);

            qDebug()<<"[Socket]sendAndReset:write长度/缓存长度:"
                   <<sendDataLength<<"/"<<m_baOutBuffer.length();

            clearOutData(true);

            //clearOutData();

            //cachePos = 0;
            return sendDataLength;
        }
        else
            return 0;
    }


protected slots:
//发送带this的相应信号
    virtual void onHostFound();
    virtual void onConnected();
    virtual void onDisconnected();  //!!!鹰:切记直接删除对象,不会发送disconnect信号!!!
    virtual void onReadyRead();
    virtual void onStateChanged(QAbstractSocket::SocketState);
    virtual void onError(QAbstractSocket::SocketError);

    virtual void onBytesWritten(qint64 bytes);
    virtual void onAboutToClose();
    virtual void onReadChannelFinished();

signals:
//以下分别是从继承的signals,只是加入了this指针
    void s_hostFound(TcpSocket*);
    void s_connected(TcpSocket*);
    void s_disconnected(TcpSocket*);
    void s_stateChanged(QAbstractSocket::SocketState, TcpSocket*);
    void s_error(QAbstractSocket::SocketError, TcpSocket*);

    void s_readyRead(TcpSocket*);
    void s_bytesWritten(qint64 bytes, TcpSocket*);
    void s_aboutToClose(TcpSocket*);
    void s_readChannelFinished(TcpSocket*);


//数据
public:
    //ip和port
    QString m_strIP;
    quint16 m_unPort;

    bool    m_AutoConnect;  //是否自动连接(send时)

    //用户数据
    void    *m_pData;


public:
    QByteArray m_baPackageHeaderFlag;   //数据包的头标志(用来验包)
    quint8 m_ucInControlByte;               //接受的数据包的控制字符

    QList<QByteArray> m_listOutCodeData;  //没有Code时的数据队列
    QList<qint16> m_listOutTransfersCode;        //Code发送时使用的Code

    //int cachePos;       //网断时缓存数据,上一条数据包的位置
    //bool cacheWriteFlag;    //网络不可用时,是否缓存数据

private:

    QByteArray m_baOutBlock;   //存 发送数据
    QDataStream m_dsOut;    //发送数据类(绑定了m_baOutBlock)
    bool m_bOutFlag;       //是否在存 发送数据 状态
    QByteArray m_baOutBuffer;  //发送缓冲
    //QMutex mutexOutBlock;   //发送缓存锁

    QDataStream m_dsIn;     //接受类(绑定了this)
    quint32 m_unInBlockSize;      //要接受的数据大小
    //bool inFlag;        //是否可以接受
    int m_nStep;       //读取一个包时的步骤
    //QTcpSocket * m_pTcpSocket;
    //quint16 nextBlockSize;
    int m_nMaxDataCacheSize;   //最大 接受数据缓存的个数(超过会释放无用数据)

//心跳数据
    QTimer m_timerHeart;
    QByteArray m_baHeart;
    //const char *m_pDataHeart;
    //int m_nDataHeartLength;

protected:

};


/*
    QByteArray m_baOutBlock;
    QDataStream m_dsOut(&m_baOutBlock, QIODevice::WriteOnly);
    m_dsOut.setVersion(QDataStream::Qt_5_3);
    m_dsOut << (quint16)0;
    //m_dsOut << text;
    m_dsOut.device()->seek(0);
    m_dsOut << (quint16)(m_baOutBlock.size() - sizeof(quint16));

    pTcpSocket->write(m_baOutBlock);
 */

#endif // CLIENTSOCKET_H
